/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufba.lasid.ds.fd;

import br.ufba.lasid.base.api.IResource;
import br.ufba.lasid.comm.api.IHost;
import br.ufba.lasid.ds.fd.api.IFailureDetector;
import br.ufba.lasid.ds.fd.api.IFailureDetector.ModuleTypes;
import br.ufba.lasid.ds.fd.api.IFailureDetectorTimeoutHandler;
import br.ufba.lasid.utils.Agenda;
import br.ufba.lasid.utils.Schedule;
import java.util.Calendar;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author aliriosa
 */
public class FailureDetectorTimeoutHandler implements IFailureDetectorTimeoutHandler{
    protected IFailureDetector detector;
    protected long timeout = 1000L;
    protected Schedule schedule;
    private static final Log logger = LogFactory.getLog(FailureDetectorTimeoutHandler.class);

    public FailureDetectorTimeoutHandler() {
    }

    public FailureDetectorTimeoutHandler(IFailureDetector detector) {
        this.detector = detector;
    }
       
    @Override
    public ModuleTypes getModuleType() throws Exception {
        return ModuleTypes.TimeoutHandler;
    }

    @Override
    public void setFailureDetector(IFailureDetector detector) throws Exception {
        this.detector = detector;
    }

    @Override
    public void startup() throws Exception {
        new Thread(this).start();
    }
    
    @Override
    public void run() {
        try{
            final Agenda agenda = detector.getAgenda();
            while(true){
                synchronized(agenda){
                    if(!(!agenda.isEmpty() && !agenda.hasSchedulePending())){
                        agenda.wait();
                    }
                }
                
                Agenda scheduled = new Agenda();
                
                synchronized(agenda){                                
                    scheduled.putAll(agenda);
                    agenda.notify();                            
                }    
                
                for(Schedule s : scheduled.values()){                    
                    if(!s.isScheduled()){
                        new Thread(new Timer(s)).start();
                    }
                }
                
            }
        }catch(Exception ex){
            logger.error("runtime error on Timeout Handler.", ex);            
        }
    }

    @Override
    public String getName() throws Exception {
        return getModuleType().name();
    }

    @Override
    public void setTimeout(long timeout) throws Exception {
        this.timeout = timeout;
    }

    @Override
    public long getTimeout() throws Exception {
        return this.timeout;
    }
    
    class Timer implements Runnable{

        final Schedule schedule;

        public Timer(Schedule schedule) {
            this.schedule = schedule;
        }                
        
        @Override
        public void run() {            
            try{
                while(true){
                    /**
                     * if our schedule is empty, then we'll have to wait.
                     */                    
                    synchronized(this.schedule){
                        if(this.schedule.isEmpty()){
                            this.schedule.wait();
                            continue;
                        }                        
                    }
                    
                    long time = this.schedule.firstKey();
                    long now  = Calendar.getInstance().getTimeInMillis();

                    /**
                     * if the current timeout hasn't expired yet, we'll wait.
                     */
                    if(now < time){
                        Thread.sleep(time - now);
                    }else{
                        /**
                         * Otherwise, we remove such a timeout from schedule and 
                         * change the related host state to down.
                         */
                        synchronized(this.schedule){
                            IHost host = (IHost)this.schedule.get(time);
                            host.setState(IResource.States.Down);
                            this.schedule.remove(time);
                        }
                    }                
                }
            }catch(Exception ex){
                logger.error("runtime error on Timeout Handler.", ex);            
            }
        }
        
    }
    
}
